---
title: "OpenFeign"
date: 2020-2-14
categories:
- java
tags:
---

<div id="content">
<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org25d9b79">Spring OpenFeign</a>
<ul>
<li><a href="#orgd0bf19c">配置流程</a></li>
<li><a href="#orgd90b553">组件</a>
<ul>
<li><a href="#orgaeddd5e">Contract</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#orgd12a25a">Timeout</a></li>
<li><a href="#org61cd5bf">FAQ</a>
<ul>
<li><a href="#org8f408c3">为什么 RequestParam 需要加上 value，它不能通过参数名识别吗</a></li>
</ul>
</li>
</ul>
</div>
</div>
<div class="outline-2" id="outline-container-org25d9b79">
<h2 id="org25d9b79">Spring OpenFeign</h2>
<div class="outline-text-2" id="text-org25d9b79">
</div>
<div class="outline-3" id="outline-container-orgd0bf19c">
<h3 id="orgd0bf19c">配置流程</h3>
<div class="outline-text-3" id="text-orgd0bf19c">
<p>
org.springframework.cloud.openfeign.FeignClientsRegistrar 被注解 @EnableFeignClients import 进来。
FeignClientsRegistrar 注册所有的 @FeignClient 注解的类成为Bean，这样这些 @FeignClient 的类就能 Autowired，构造注入等。
这些Bean定义为使用工厂FeignClientFactoryBean创建。
</p>
<p>
注解@FeignClient(configuration = Some.Class)中的configuration参数能自定义这个FeignClient的配置，使这个client的 Encoder, Decoder等都能单独配置。
configuration参数值没有被直接注入到容器中，而是注入成一堆 FeignClientSpecification 。
这一堆 FeignClientSpecification 被注入到 org.springframework.cloud.openfeign.FeignAutoConfiguration 中，然后被设置到 FeignContext 中。
</p>
<p>
FeignContext 是一个NamedContextFactory，它的主要内容是一个 {% raw %} private Map&lt;String, AnnotationConfigApplicationContext&gt; contexts = new ConcurrentHashMap&lt;&gt;(); {% endraw %} ，
就是一张 FeignClient 与它的子容器的映射表。当上面的一堆FeignClientSpecificatin设置到FeignContext之后，FeignContext在contexts中创建一条条的关系（不是立即创建），
</p>
<ul class="org-ul">
<li>key为FeignClient的contextId</li>
<li>value为一个子容器，子容器里面是 @FeignClient(configuration=…) 指定的配置类中的Bean。</li>
</ul>
<p>
最终在 org.springframework.cloud.openfeign.FeignClientFactoryBean 中Build成feign客户端。它会优先从上面的 FeignContext 中取出客户端的子容器的 Encoder, Decoder等对象，构建Target。
</p>
</div>
</div>
<div class="outline-3" id="outline-container-orgd90b553">
<h3 id="orgd90b553">组件</h3>
<div class="outline-text-3" id="text-orgd90b553">
</div>
<div class="outline-4" id="outline-container-orgaeddd5e">
<h4 id="orgaeddd5e">Contract</h4>
<div class="outline-text-4" id="text-orgaeddd5e">
<p>
定义Feign接口中哪些注解和值是有效的。Spring实现了一个SpringMvcContract来支持自己的@RequestParam @PathVariable等注解。
</p>
</div>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-orgd12a25a">
<h2 id="orgd12a25a">Timeout</h2>
<div class="outline-text-2" id="text-orgd12a25a">
<p>
Class file: feign/SynchronousMethodHandler.java，根据是否禁用ribbon，client的实现类是不一样的。
</p>
<p>
如果classpath中包含了ribbon，则ribbon默认会启用，但是ribbon的超时时间暂时没有找到设置方法。
</p>
<p>
而项目启动时Spring Cloud会提示cloud-ribbon处理维护期，建议禁用，如果确实不需要ribbon，则通过 spring.cloud.loadbalancer.ribbon.enabled=false 禁用。
禁用后feign默认的readTimeout为60秒，通过下面的配置指定超时时间：
</p>
<div class="org-src-container">
<pre class="src src-yaml">feign:
  client:
    config:
      default:
	readTimeout: 10000
	connectTimeout: 5000
</pre>
</div>
<p>
与这个配置有关的类为 {% raw %} BlockingLoadBalancerClientAutoConfiguration {% endraw %} 其中 BlockingLoadbalancerClientConfig 会在 spring.cloud.loadbalancer.ribbon.enabled=false时执行，
而如果执行了，则已存在一个LoadBalancerClient Bean，所以RibbonLoadBalancerClient不会被创建：
</p>
<div class="org-src-container">
<pre class="src src-java">
<span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">org/springframework/cloud/netflix/ribbon/RibbonAutoConfiguration.java</span>
<span style="font-weight: bold; text-decoration: underline;">@Bean</span>
<span style="font-weight: bold; text-decoration: underline;">@ConditionalOnMissingBean</span>(LoadBalancerClient.<span style="font-weight: bold;">class</span>)
<span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">LoadBalancerClient</span> <span style="font-weight: bold;">loadBalancerClient</span>() {
    <span style="font-weight: bold;">return</span> <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">RibbonLoadBalancerClient</span>(springClientFactory());
}

<span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">org/springframework/cloud/loadbalancer/config/BlockingLoadBalancerClientAutoConfiguration.java</span>
<span style="font-weight: bold; text-decoration: underline;">@Configuration</span>(proxyBeanMethods = <span style="font-weight: bold; text-decoration: underline;">false</span>)
<span style="font-weight: bold; text-decoration: underline;">@ConditionalOnClass</span>(RestTemplate.<span style="font-weight: bold;">class</span>)
<span style="font-weight: bold; text-decoration: underline;">@Conditional</span>(OnNoRibbonDefaultCondition.<span style="font-weight: bold;">class</span>)
<span style="font-weight: bold;">protected</span> <span style="font-weight: bold;">static</span> <span style="font-weight: bold;">class</span> <span style="font-weight: bold; text-decoration: underline;">BlockingLoadbalancerClientConfig</span> {

    <span style="font-weight: bold; text-decoration: underline;">@Bean</span>
    <span style="font-weight: bold; text-decoration: underline;">@ConditionalOnBean</span>(LoadBalancerClientFactory.<span style="font-weight: bold;">class</span>)
    <span style="font-weight: bold; text-decoration: underline;">@Primary</span>
    <span style="font-weight: bold;">public</span> <span style="font-weight: bold; text-decoration: underline;">BlockingLoadBalancerClient</span> <span style="font-weight: bold;">blockingLoadBalancerClient</span>(
                                                                 <span style="font-weight: bold; text-decoration: underline;">LoadBalancerClientFactory</span> <span style="font-weight: bold; font-style: italic;">loadBalancerClientFactory</span>) {
        <span style="font-weight: bold;">return</span> <span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">BlockingLoadBalancerClient</span>(loadBalancerClientFactory);
    }

}

</pre>
</div>
<p>
注意readTimeout和connectTimeout都需要指定，只指定一个都不会生效：
</p>
<div class="org-src-container">
<pre class="src src-java"><span style="font-weight: bold; font-style: italic;">// </span><span style="font-weight: bold; font-style: italic;">org/springframework/cloud/openfeign/FeignClientFactoryBean.java:172</span>
<span style="font-weight: bold;">if</span> (config.getConnectTimeout() != <span style="font-weight: bold; text-decoration: underline;">null</span> &amp;&amp; config.getReadTimeout() != <span style="font-weight: bold; text-decoration: underline;">null</span>) {
    builder.options(<span style="font-weight: bold;">new</span> <span style="font-weight: bold; text-decoration: underline;">Request</span>.<span style="font-weight: bold; text-decoration: underline;">Options</span>(config.getConnectTimeout(),

}
</pre>
</div>
</div>
</div>
<div class="outline-2" id="outline-container-org61cd5bf">
<h2 id="org61cd5bf">FAQ</h2>
<div class="outline-text-2" id="text-org61cd5bf">
</div>
<div class="outline-3" id="outline-container-org8f408c3">
<h3 id="org8f408c3">为什么 RequestParam 需要加上 value，它不能通过参数名识别吗</h3>
<div class="outline-text-3" id="text-org8f408c3">
<blockquote>
<p>
<a href="https://stackoverflow.com/questions/44313482/feign-client-with-spring-boot-requestparam-value-was-empty-on-parameter-0">https://stackoverflow.com/questions/44313482/feign-client-with-spring-boot-requestparam-value-was-empty-on-parameter-0</a>
</p>
</blockquote>
<p>
Unfortunately, javac compiler omits the debug information of parameter name from class file for java interfaces. That's why feign fails to find parameter name without -parameter.
</p>
<p>
对于 interface, java编译器会忽略掉参数名，所以如果编译时不加上 -parameter 参数，openfeign也无法获取参数的名称了。
</p>
<p>
如果项目继承了 spring-boot-starter-parent，注解的value就不需要写了。因为在spring-boot-starter-parent的pom里面有这样一段：
</p>
<div class="org-src-container">
<pre class="src src-xml">&lt;<span style="font-weight: bold;">plugin</span>&gt;
  &lt;<span style="font-weight: bold;">artifactId</span>&gt;maven-compiler-plugin&lt;/<span style="font-weight: bold;">artifactId</span>&gt;
  &lt;<span style="font-weight: bold;">configuration</span>&gt;
    &lt;<span style="font-weight: bold;">parameters</span>&gt;true&lt;/<span style="font-weight: bold;">parameters</span>&gt;
    &lt;/<span style="font-weight: bold;">configuration</span>&gt;
 &lt;/<span style="font-weight: bold;">plugin</span>&gt;
</pre>
</div>
</div>
</div>
</div>
</div>
<div class="status" id="postamble">
<p class="date">Date: 2020-2-14</p>
<p class="author">Author: gdme1320</p>
<p class="validation"><a href="http://validator.w3.org/check?uri=referer">Validate</a></p>
</div>
